package com.tsinjie.metadata.java;

import com.tsinjie.metadata.database.model.Column;
import com.tsinjie.metadata.database.model.Database;
import com.tsinjie.metadata.database.model.ExportColumn;
import com.tsinjie.metadata.database.model.ImportColumn;
import com.tsinjie.metadata.database.model.Table;
import com.tsinjie.metadata.java.model.JavaClass;
import com.tsinjie.metadata.java.model.JavaField;
import com.tsinjie.metadata.java.model.JavaProject;
import com.tsinjie.metadata.java.model.hibernate.ManyToMany;
import com.tsinjie.metadata.java.model.hibernate.ManyToOne;
import com.tsinjie.metadata.java.model.hibernate.OneToMany;
import com.tsinjie.metadata.java.model.hibernate.OneToOne;
import com.tsinjie.metadata.util.ConvertUtils;
import org.apache.commons.beanutils.BeanUtils;

import java.util.Date;
import java.util.List;

/**
 * Created by LiuXunming on 2015-10-14.
 * 根据Database对象构建java工程
 */
public class DatabaseJavaProjectBuilder extends AbstractJavaProjectBuilder<Database> {

    private JavaProject javaProject = null;

    public static final String USERNAME_FIELD = "username";

    public DatabaseJavaProjectBuilder(Database database) {
        super(database);
        if (database == null) {
            throw new IllegalArgumentException("database is null.");
        }
        super.hasModule = analysisContainModule();
    }


    /**
     * 构建JavaProject对象
     * {@link com.tsinjie.metadata.java.model.JavaProject}
     *
     * @return JavaProject
     * @throws Exception
     */
    @Override
    protected JavaProject javaProject() throws Exception {
        javaProject = new JavaProject();
        BeanUtils.copyProperties(javaProject, source);
        javaProject.setCreateDate(new Date());
        StringBuilder basePackage = new StringBuilder("com");
        String projectName = javaProject.getProjectName();
        if (projectName == null) {
            throw new IllegalArgumentException("projectName is null , please check your data , make sure setProjectName() was called.");
        }
        String[] items = projectName.split(PACKAGE_NAME_SPLIT_CHAR);
        for (String str : items) {
            basePackage.append(POINT + str);
        }
        javaProject.setBasePackage(basePackage.toString());
        javaProject.setAuthor(System.getProperties().getProperty("user.name"));
        javaProject.setRemark("Generic by CodeRobot.");
        return javaProject;
    }

    /**
     * 构建java类信息列表
     * {@link com.tsinjie.metadata.java.model.JavaClass}
     *
     * @param javaProject
     * @throws Exception
     */
    @Override
    protected void javaClasses(JavaProject javaProject) throws Exception {
        List<Table> tables = source.getTables();
        JavaClass javaClass = null;
        for (Table table : tables) {//TODO 排除多主键？
            if (table.getPrimaryKeys().size() > 1) {
                continue;
            }
            javaClass = new JavaClass();
            javaClass.setTableName(table.getTableName());
            javaClass.setRemark(table.getComment());
            javaClass.setClassName(ConvertUtils.convertTableNameToClazzName(table.getTableName(), hasModule));
            String module = ConvertUtils.getModuleNameByTableName(table.getTableName(), hasModule);
            javaClass.setModule(module);
            //包含需要在渲染的时候添加具体包（这里少了一层）
            String packageName = javaProject.getBasePackage() + (module == null ? "" : "." + module);
            javaClass.setPackageName(packageName);
            javaProject.addJavaClass(javaClass);
        }
    }

    /**
     * 构建字段信息（包括主字段、外来字段、普通字段）
     * {@link com.tsinjie.metadata.java.model.JavaField}
     *
     * @param javaClasses
     * @throws Exception
     */
    @Override
    protected void fields(List<JavaClass> javaClasses) throws Exception {
        for (JavaClass javaClass : javaClasses) {
            Table table = source.getTable(javaClass.getTableName());
            //主属性字段
            List<Column> primaryKeys = table.getPrimaryKeys();
            identifies(javaClass, primaryKeys);
            //外来字段
            List<ImportColumn> importColumns = table.getImportColumns();
            foreignFields(javaClass, importColumns);
            //普通字段
            List<Column> columns = table.getColumns();
            normalFields(javaClass, columns);
        }
    }

    /**
     * 构建hibernate关系
     * 1、一对一副表（只对应一个主表）
     * 2、一对一主表（可以被多个表引用）
     * 3、一对多
     * 4、多对一
     * 5、多对多
     * {@link com.tsinjie.metadata.java.model.hibernate.OneToOne}
     * {@link com.tsinjie.metadata.java.model.hibernate.OneToMany}
     * {@link com.tsinjie.metadata.java.model.hibernate.ManyToOne}
     * {@link com.tsinjie.metadata.java.model.hibernate.ManyToMany}
     *
     * @param javaClasses
     * @throws Exception
     */
    @Override
    protected void hibernateRelationship(List<JavaClass> javaClasses) throws Exception {
        for (JavaClass javaClass : javaClasses) {
            Table table = source.getTable(javaClass.getTableName());
            //引用键（当前表的外键）
            List<ImportColumn> importColumns = table.getImportColumns();
            importRelationship(javaClass, importColumns, table);
            //被引用键（其他表外键参考键）
            List<ExportColumn> exportColumns = table.getExportColumns();
            exportRelationship(javaClass, exportColumns, table);
        }
    }

    /**
     * 分析是否包含模块
     *
     * @return
     */
    @Override
    protected boolean analysisContainModule() {
        List<Table> tables = source.getTables();
        if (tables == null || tables.size() == 0) {
            throw new IllegalArgumentException("tables is null or is empty.");
        }
        for (Table table : tables) {
            String tableName = table.getTableName();
            if (tableName == null) {
                throw new IllegalArgumentException("tableName is null , please check your data , make sure setTableName() was called.");
            }
            if (tableName.split(TABLE_NAME_SPLIT_CHAR).length <= 2) {
                return false;
            }
        }
        return true;
    }

    /**
     * 主属性
     *
     * @param javaClass
     * @param primaryKeys
     * @throws InstantiationException
     * @throws IllegalAccessException
     */
    private void identifies(JavaClass javaClass, List<Column> primaryKeys) throws InstantiationException, IllegalAccessException {
        if (primaryKeys != null) {
            for (Column pk : primaryKeys) {
                //主键为隐藏域
                JavaField javaField = buildJavaField(JavaField.class, javaClass, pk);
                checkLoginClass(javaField.getFieldName(), javaClass);
                javaField.setHtmlShowType("hidden");
                javaClass.addIdentify(javaField);
            }
        }
    }

    /**
     * 外来字段信息
     *
     * @param javaClass
     * @param importColumns
     * @throws InstantiationException
     * @throws IllegalAccessException
     */
    private void foreignFields(JavaClass javaClass, List<ImportColumn> importColumns) throws InstantiationException, IllegalAccessException {
        if (importColumns != null) {
            for (ImportColumn ic : importColumns) {
                JavaField javaField = buildJavaField(JavaField.class, javaClass, ic);
                checkLoginClass(javaField.getFieldName(), javaClass);
                //外键字段为下拉选项
                javaField.setHtmlShowType("select");
                javaClass.addForeignField(javaField);
            }
        }
    }

    /**
     * 普通字段信息
     *
     * @param javaClass
     * @param columns
     * @throws InstantiationException
     * @throws IllegalAccessException
     */
    private void normalFields(JavaClass javaClass, List<Column> columns) throws InstantiationException, IllegalAccessException {
        if (columns != null) {
            for (Column c : columns) {
                JavaField javaField = buildJavaField(JavaField.class, javaClass, c);
                checkLoginClass(javaField.getFieldName(), javaClass);
                String type = javaField.getType();
                switch (type) {
                    case "Date":
                        javaField.setHtmlShowType("date");
                        break;
                    case "String":
                        if (javaField.getLength() != null && javaField.getLength() > 255) {
                            javaField.setHtmlShowType("textarea");
                        }
                        break;
                    case "BigDecimal":
                    case "Long":
                    case "Integer":
                        javaField.setHtmlShowType("number");
                        break;
                }
                javaClass.addNormalField(javaField);
            }
        }
    }

    /**
     * 引用关系<br/>
     * 一对一作为副表，多对一情况
     *
     * @param javaClass
     * @param importColumns
     * @param table
     * @throws IllegalAccessException
     * @throws InstantiationException
     */
    private void importRelationship(JavaClass javaClass, List<ImportColumn> importColumns, Table table) throws IllegalAccessException, InstantiationException {
        if (importColumns == null) {
            return;
        }
        //当前表主键名称
        String primaryKeyName = table.getPrimaryKey().getColumnName();
        //当前表的外键列表
        for (ImportColumn ic : importColumns) {
            //外键参考表
            Table exportTable = source.getTable(ic.getExportTableName());
            //外键字段名
            String columnName = ic.getColumnName();
                /*主外键相同，一对一*/
            if (primaryKeyName.equals(columnName)) {
                oneToOne(javaClass, exportTable, ic);
            } else {//多对一
                manyToOne(javaClass, exportTable, ic, exportTable.equals(table));
            }
        }
    }

    /**
     * 被引用关系<br/>
     * 一对一作为主表，一对多，多对多
     *
     * @param javaClass
     * @param exportColumns
     * @param table
     */
    private void exportRelationship(JavaClass javaClass, List<ExportColumn> exportColumns, Table table) {
        if (exportColumns == null) {
            return;
        }
        for (ExportColumn ec : exportColumns) {
            //源表
            Table importTable = source.getTable(ec.getImportTableName());
            List<Column> primaryKeys = importTable.getPrimaryKeys();
            //源表有两个主键，中间表，多对多
            if (primaryKeys.size() == 2) {
                manyToMany(javaClass, table, importTable, ec);
            } else {
                 /*源表主键名称*/
                String importTablePrimaryKeyName = importTable.getPrimaryKey().getColumnName();
                /*源表引用键名称*/
                String importColumnName = ec.getImportColumnName();
                if (importTablePrimaryKeyName.equals(importColumnName)) {//一对一作为主表（源表主键==外键）
                    oneToOnes(javaClass, importTable, ec);
                } else {
                    oneToMany(javaClass, importTable, ec, importTable.equals(table));
                }
            }
        }
    }


    /**
     * 一对一副表（只对应一个主表）
     * {@link com.tsinjie.metadata.java.model.hibernate.OneToOne}
     *
     * @param javaClass
     * @param exportTable
     * @param importColumn
     */
    private void oneToOne(JavaClass javaClass, Table exportTable, ImportColumn importColumn) {
        OneToOne oneToOne = new OneToOne();
        String type = ConvertUtils.convertTableNameToClazzName(importColumn.getExportTableName(), hasModule);
        oneToOne.setType(type);
        oneToOne.setName(ConvertUtils.lowerCaseFirstChar(type, false));
        oneToOne.setRemark(exportTable.getComment());
        oneToOne.setMappedBy(ConvertUtils.lowerCaseFirstChar(javaClass.getClassName(), false));
        javaClass.setOneToOne(oneToOne);
    }

    /**
     * * 多对一
     * {@link com.tsinjie.metadata.java.model.hibernate.ManyToOne}
     *
     * @param javaClass
     * @param exportTable
     * @param importColumn
     * @param treeStructure 是否为树形结构表
     * @throws InstantiationException
     * @throws IllegalAccessException
     */
    private void manyToOne(JavaClass javaClass, Table exportTable, ImportColumn importColumn, boolean treeStructure) throws InstantiationException, IllegalAccessException {
        ManyToOne manyToOne = new ManyToOne();
        String exportTableName = exportTable.getTableName();
        JavaClass exportClass = javaProject.getJavaClass(exportTableName);
        manyToOne.setClassModule(exportClass.getModule());
        String type = ConvertUtils.convertTableNameToClazzName(exportTableName, hasModule);
        String name = null;
        String remark = null;
        /*父子关系表（树形结构表）*/
        if (treeStructure) {
            name = "parent";
            remark = "父" + exportTable.getComment();
        } else {
            name = ConvertUtils.lowerCaseFirstChar(type, false);
            remark = exportTable.getComment();
        }
        manyToOne.setType(type);
        manyToOne.setRemark(remark);
        manyToOne.setName(name);
        manyToOne.setJoinColumn(importColumn.getColumnName());
        buildJavaField(manyToOne, javaClass, importColumn);
        javaClass.addManyToOne(manyToOne);
    }

    /**
     * 一对一主表（可以被多个表引用）
     * {@link com.tsinjie.metadata.java.model.hibernate.OneToOne}
     *
     * @param javaClass
     * @param importTable
     * @param exportColumn
     * @throws Exception
     */
    private void oneToOnes(JavaClass javaClass, Table importTable, ExportColumn exportColumn) {
        OneToOne oneToOne = new OneToOne();
        String type = ConvertUtils.convertTableNameToClazzName(exportColumn.getImportTableName(), hasModule);
        oneToOne.setType(type);
        oneToOne.setName(ConvertUtils.lowerCaseFirstChar(type, false));
        oneToOne.setRemark(importTable.getComment());
        oneToOne.setMappedBy(ConvertUtils.lowerCaseFirstChar(javaClass.getClassName(), false));
        javaClass.addOneToOne(oneToOne);
    }

    /**
     * 一对多
     * {@link com.tsinjie.metadata.java.model.hibernate.OneToMany}
     *
     * @param javaClass
     * @param importTable
     * @param exportColumn
     * @param treeStructure
     */
    private void oneToMany(JavaClass javaClass, Table importTable, ExportColumn exportColumn, boolean treeStructure) {
        OneToMany oneToMany = new OneToMany();
        String type = ConvertUtils.convertTableNameToClazzName(exportColumn.getImportTableName(), hasModule);
        String importTableName = importTable.getTableName();
        JavaClass importClass = javaProject.getJavaClass(importTableName);
        oneToMany.setClassModule(importClass.getModule());
        String name = null;
        String remark = null;
        /*父子关系表（树形结构表）*/
        if (treeStructure) {
            name = "children";
            remark = "子" + importTable.getComment();
            oneToMany.setMappedBy("parent");
        } else {
            name = ConvertUtils.lowerCaseFirstChar(type, false);
            //单词复数
            name = ConvertUtils.convertToPlural(name);
            remark = importTable.getComment() + "列表";
            oneToMany.setMappedBy(ConvertUtils.lowerCaseFirstChar(javaClass.getClassName(), false));
        }
        oneToMany.setType(type);
        oneToMany.setName(name);
        oneToMany.setRemark(remark);
        javaClass.addOneToMany(oneToMany);
        javaClass.addImportPackage("java.util.List");
        javaClass.addImportPackage("java.util.ArrayList");
    }


    /**
     * 多对多
     * {@link com.tsinjie.metadata.java.model.hibernate.ManyToMany}
     *
     * @param javaClass
     * @param table        当前表（被引用表）
     * @param importTable  引用表（中间表）
     * @param exportColumn
     * @throws Exception
     */
    private void manyToMany(JavaClass javaClass, Table table, Table importTable, ExportColumn exportColumn) {
        //中间表外键
        List<ImportColumn> importColumns = importTable.getImportColumns();
        for (ImportColumn importColumn : importColumns) {
            //中间表参考表名称
            String exportTableName = importColumn.getExportTableName();
            if (exportTableName.equals(table.getTableName())) {
                continue;
            }
            ManyToMany manyToMany = new ManyToMany();
            //中间表参考表
            Table exportTable = source.getTable(exportTableName);
            //类名，首字母大写
            String mtoTypeName = ConvertUtils.convertTableNameToClazzName(exportTable.getTableName(), hasModule);
            //变量名，首字母小写
            String mtoVarName = ConvertUtils.lowerCaseFirstChar(mtoTypeName, false);
            //单词设置为复数
            mtoVarName = ConvertUtils.convertToPlural(mtoVarName);
            manyToMany.setName(mtoVarName);
            manyToMany.setType(mtoTypeName);
            JavaClass exportClass = javaProject.getJavaClass(exportTableName);
            manyToMany.setClassModule(exportClass.getModule());
            //多对多属性
            manyToMany.setJoinTable(importTable.getTableName());
            manyToMany.setJoinColumns(exportColumn.getImportColumnName());
            manyToMany.setInverseJoinColumns(importColumn.getColumnName());
            manyToMany.setRemark(importTable.getComment());
            javaClass.addManyToMany(manyToMany);
            javaClass.addImportPackage("java.util.List");
            javaClass.addImportPackage("java.util.ArrayList");
            break;
        }
    }


    /**
     * 构建java字段
     *
     * @param clazz
     * @param javaClass
     * @param column
     * @param <T>
     */
    private <T extends JavaField> T buildJavaField(Class<T> clazz, JavaClass javaClass, Column column) throws IllegalAccessException, InstantiationException {
        T t = clazz.newInstance();
        buildJavaField(t, javaClass, column);
        return t;
    }

    /**
     * 构建java字段
     *
     * @param t
     * @param javaClass
     * @param column
     * @param <T>
     */
    private <T extends JavaField> void buildJavaField(T t, JavaClass javaClass, Column column) throws IllegalAccessException, InstantiationException {
        if (t.getName() == null) {
            t.setName(ConvertUtils.convertColumnNameToFieldName(column.getColumnName()));
        }
        if (t.getType() == null) {
            String type = ConvertUtils.convertSQLTypeToJavaType(column.getType());
            t.setType(type);
        }
        if (t.getType().equals("Date")) {
            javaClass.addImportPackage("java.util.Date");
        } else if (t.getType().equals("BigDecimal")) {
            javaClass.addImportPackage("java.math.BigDecimal");
        }
        if (t.getRemark() == null) {
            t.setRemark(column.getComment());
        }
        t.setFieldName(column.getColumnName());
        t.setJdbcType(ConvertUtils.convertSQLTypeToJdbcType(column.getType()));
        t.setNullable(column.getNullable());
        t.setDefaultValue(column.getDefaultValue());
        t.setLength(column.getLength());
    }

    /**
     * 判断是否为登录类，包含username 就认定是？
     *
     * @param fieldName
     * @param javaClass
     */
    private void checkLoginClass(String fieldName, JavaClass javaClass) {
        if (fieldName.equals(USERNAME_FIELD)) {//TODO 包含username就认定为登录表？
            javaClass.setLoginClass(JavaClass.LOGIN_CLASS);
        }
    }
}
